/**
 * 
 */
package com.tcl.wip.client.repository;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.aop.framework.ProxyFactory;

import com.google.common.base.CaseFormat;
import com.tcl.wip.client.WipApp;
import com.tcl.wip.client.annotation.GenerationStrategy;
import com.tcl.wip.client.annotation.Id;
import com.tcl.wip.client.annotation.ShardingKey;
import com.tcl.wip.client.annotation.Table;
import com.tcl.wip.client.common.utils.ClassUtils;
import com.tcl.wip.client.exception.EntityValidateException;
import com.tcl.wip.client.orm.UUIDGenerater;
import com.tcl.wip.client.orm.WipSequenceIdGenerater;

/**
 * @author zhaowen.zhuang
 * @Date Jan 20, 2015
 */
public class WipRepositoryFactory {

    @SuppressWarnings("unchecked")
    public <T> T createRepository(Class<T> clazz, WipApp app) {
        DatasourceManager datasourceManager = app.getDatasourceManager();
        Type[] types = ClassUtils.getInterfaceGenricType(clazz, WipRepository.class);
        if (types == null || types.length != 3) {
            throw new RuntimeException("cat't get generic type form class " + clazz);
        }
        EntityMeta meta = parseEntity(types[0]);

        if (meta.getIdField() != null) {
            Id id = meta.getIdField().getAnnotation(Id.class);
            GenerationStrategy strategy = id.generationStrategy();
            if (strategy == GenerationStrategy.UUID) {
                meta.setIdGenerator(new UUIDGenerater());
            } else if (strategy == GenerationStrategy.WIP_SEQUENCE) {
                meta.setIdGenerator(new WipSequenceIdGenerater(app, meta.getTableName()));
            }
        }


        validateEntity(meta);

        SimpleWipRepository<Object, Object, Object> target = new SimpleWipRepository<>(meta, datasourceManager);
        ProxyFactory factory = new ProxyFactory();
        factory.setTarget(target);
        factory.setInterfaces(new Class<?>[] {clazz, WipRepository.class});
        return (T) factory.getProxy();
    }

    private EntityMeta parseEntity(Type type) {
        EntityMeta meta = new EntityMeta();
        Class<?> clazz = (Class<?>) type;
        meta.setEntityType(clazz);

        Map<String, String> map = new HashMap<String, String>();
        List<Field> propertyFields = new ArrayList<>();
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            if (Modifier.isStatic(field.getModifiers())) {
                continue;
            }

            map.put(CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, field.getName()),
                    field.getName());

            field.setAccessible(true);

            if (field.isAnnotationPresent(Id.class)) {
                meta.setIdField(field);
            }

            if (field.isAnnotationPresent(ShardingKey.class)) {
                meta.setShardingKeyField(field);
            }

            if (field.isAnnotationPresent(Id.class) || field.isAnnotationPresent(ShardingKey.class)) {
                continue;
            }

            propertyFields.add(field);
        }

        meta.setColumnToPropertyMap(Collections.unmodifiableMap(map));
        meta.setPropertyFields(Collections.unmodifiableList(propertyFields));

        Table annotation = clazz.getAnnotation(Table.class);
        if (annotation != null) {
            meta.setTableName(annotation.value());
        } else {
            meta.setTableName(CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE,
                    clazz.getSimpleName()));
        }

        return meta;
    }

    public void validateEntity(EntityMeta meta) {
        if (meta.getIdField() == null) {
            throw new EntityValidateException("wip entity must contain id field annotation by @Id");
        }

        if (meta.getShardingKeyField() == null) {
            throw new EntityValidateException(
                    "wip entity must contain shardingKey field annotation by @ShardingKey");
        }

    }
}
